/**
 * small-spring
 *
 * @author yuanchen
 * @date 2021-06-29 16:16
 */
package cn.bugstack.springframework.beans.factory.support;

import cn.bugstack.springframework.beans.factory.config.BeanDefinition;
import cn.bugstack.springframework.beans.factory.config.BeanReference;
import cn.bugstack.springframework.beans.factory.config.PropertyValues;
import cn.bugstack.springframework.exception.BeansException;
import cn.bugstack.springframework.utils.BeanUtil;

import java.lang.reflect.Constructor;
import java.util.Objects;

/**
 *
 * @author yuanchen
 * @date 2021-06-29 16:16
 */
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory{

    private InstantiationStrategy instantiationStrategy=new CglibSubclassingInstantiationStrategy();

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) {
        Object bean = null;
        try {
            bean = createBeanInstance(beanName,beanDefinition,args);

        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        if(Objects.isNull(bean)){
            return null;
        }

        try {
            applyPropertyValues(beanName,bean,beanDefinition);
        } catch (Exception be) {
            throw new BeansException(" error applyPropertyValues");
        }


        if(Objects.nonNull(bean)&&beanDefinition.isSingleton()){
            addSingleton(beanName,bean);
        }

        return bean;
    }

    private void applyPropertyValues(String beanName, Object bean,BeanDefinition beanDefinition) {

        try {

            PropertyValues propertyValues = beanDefinition.getPropertyValues();

            if(Objects.isNull(propertyValues)){
                return;
            }

            propertyValues.getPropertyValueList().forEach(s->{

                String name=s.getName();

                Object value=s.getValue();

                if(value instanceof BeanReference){
                    BeanReference beanReference= (BeanReference) value;
                    value=getBean(beanReference.getBeanName());
                }

                BeanUtil.setFieldValue(bean,name,value);

            });


        } catch (Exception be) {
            throw new BeansException("error applyPropertyValues:"+beanName);
        }
    }

    protected Object createBeanInstance(String beanName, BeanDefinition beanDefinition, Object[] args){

        Constructor constructor=getConstructor(beanDefinition,args);

        return instantiationStrategy.instantiate(beanDefinition,beanName,constructor,args);
    }

    protected Constructor getConstructor(BeanDefinition beanDefinition, Object[] args){

        if(Objects.isNull(args)){
            return null;
        }

        Constructor[] constructors = beanDefinition.getBeanClass().getConstructors();

        for (Constructor ctor: constructors) {
            if(checkParameterTypes(ctor.getParameterTypes(),args)){
                return ctor;
            }
        }

        return null;
    }

    protected boolean checkParameterTypes(Class<?>[] parameterTypes,Object[] args){

        if(parameterTypes.length!=args.length){
            return false;
        }

        for (int i = 0; i < args.length; i++) {
            if(!parameterTypes[i].isAssignableFrom(args[i].getClass())){
                return false;
            }
        }

        return true;
    }

    public InstantiationStrategy getInstantiationStrategy() {
        return instantiationStrategy;
    }

    public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) {
        this.instantiationStrategy = instantiationStrategy;
    }
}
